package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.enums.DeviceLocationTypeEnum;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author sjqn
 */
@Slf4j
@Service
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private DeviceMapper deviceMapper;

    //设备维护人员的角色名称
    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    //超级管理员的角色名称
    @Value("${zzyl.alert.managerRole}")
    private String managerRole;

    /**
     * 新增报警规则
     * @param alertRuleDto 报警规则请求模型
     */
    @Override
    public void createRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    /**
     * 分页条件查询
     *
     * @param pageNum       页码
     * @param pageSize      页面大小
     * @param alertRuleName 报警规则名称
     * @param productKey    产品key
     * @param functionName  功能名称
     * @return 分页结果
     */
    @Override
    public PageResponse<AlertRuleVo> getAlertRulePage(Integer pageNum, Integer pageSize, String alertRuleName, String productKey, String functionName) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> page = alertRuleMapper.page(alertRuleName, productKey, functionName);
        //添加提示
        page.getResult().forEach(alertRuleVo -> {
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            alertRuleVo.setRules(alertReason);
        });

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 删除报警
     *
     * @param id 报警规则id
     */
    @Override
    public void deleteAlertRule(Long id) {
        alertRuleMapper.deleteByPrimaryKey(id);
    }

    /**
     * 修改报警
     *
     * @param id           报警规则id
     * @param alertRuleDto 报警规则更新请求模型
     */
    @Override
    public void updateAlertRule(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.updateByPrimaryKeySelective(alertRule);
    }

    /**
     * 查询报警信息
     *
     * @param id 报警规则id
     * @return 报警信息
     */
    @Override
    public AlertRuleVo readAlertRule(Long id) {
        AlertRule alertRule = alertRuleMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(alertRule, AlertRuleVo.class);
    }

    /**
     * 启用或禁用
     *
     * @param id     报警id
     * @param status 报警规则状态
     */
    @Override
    public void enableOrDisable(Long id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    /**
     * 过滤告警数据
     *
     * @param deviceDataVo 上报的数据
     */
    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {
        // 超过1分钟数据不处理, 当前系统时间-上报的时间 > 60S
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        // between(start,end) 计算的是end-start的差值,单位：ChronoUnit.SECONDS
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if(between > 60){
            //超过1分钟不处理
            log.info("超过1分钟不处理：{},{}", deviceDataVo.getIotId(), deviceDataVo.getFunctionId());
            return;
        }

        // 1、 找规则,查询所有的规则(可能会创建全部设备的规则), iot_id=-1
        // 产品下的所有规则
        List<AlertRule> allDeviceRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());
        // 该产品对应具体设备的规则
        List<AlertRule> theDeviceRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        Set<AlertRule> allRules = new HashSet<>();
        allRules.addAll(allDeviceRules);
        allRules.addAll(theDeviceRules);

        // 非空判断
        if(CollUtil.isEmpty(allRules)){
            log.info("该产品或设备下没有规则");
            return;
        }
        //如果报警规则不为空，遍历报警规则，进行校验
        allRules.forEach(alertRule -> deviceDateAlarmHandler(deviceDataVo, alertRule));
    }

    /**
     * 校验上报的数据与规则是否需要告警
     * @param deviceDataVo
     * @param alertRule
     */
    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        //判断是否在生效时间内
        // 规则的生效时间段 00:00:00~23:59:59
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] strings = alertEffectivePeriod.split("~");
        LocalTime start = LocalTime.parse(strings[0]);// 时分秒
        LocalTime end = LocalTime.parse(strings[1]);

        // 上报数据的时间，toLocalTime获取时分秒部分
        LocalTime localTime = deviceDataVo.getAlarmTime().toLocalTime();
        if(localTime.isBefore(start) || localTime.isAfter(end)){
            log.debug("不在生效的时间段内，不处理");
            return;
        }

        // 获取设备iot_id
        String iotId = deviceDataVo.getIotId();

        // 连续周期计数的key
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX +
            iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();

        //检验数据是否达到阈值, 把规则中设置的阈值与上报的数据进行对比
        // 阈值
        Float value = alertRule.getValue();
        // 上报数据
        String dataValue = deviceDataVo.getDataValue();
        Float alarmData = Convert.toFloat(dataValue);
        // 运算符
        String operator = alertRule.getOperator();
        if((">=".equals(operator) && alarmData>=value) || ("<".equals(operator) && alarmData<value)){
            // 达到阈值, 发消息通知
        }else{
            // 清空redis的连持续周期的计数
            stringRedisTemplate.delete(aggCountKey);
            return;
        }

        // 沉默周期判断, 使用redis设置key的存活时间
        String silentKey = CacheConstants.ALERT_SILENT_PREFIX +
            iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        // key存在则代表还在沉默周期内
        if (stringRedisTemplate.hasKey(silentKey)) {
            log.info("处于沉默周期内，不处理");
            return;
        }
        // key不存在,则代表不在沉默周期内，或已经过了沉默周期

        // 判断持续周期
        // 连续周期次数+1, increment让本身的值自增+1,如果key不存在则设置key并且值为1，执行后会返回最新的值
        Long count = stringRedisTemplate.opsForValue().increment(aggCountKey);
        // 判断 是否达到持续周期
        if (count < alertRule.getDuration()) {
            // 没有达到 持续周期的次数，不处理
            return;
        }
        //校验全部通过，
        //当前数据触发报警

        // 添加告警数据，
        // 添加沉默周期, 单位为分钟
        stringRedisTemplate.opsForValue().set(silentKey,"1", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        // 删除持续周期的次数
        stringRedisTemplate.delete(aggCountKey);

        // 需要通知的人员id
        List<Long> userIds = new ArrayList<>();
        //是否是老人数据
        if(1==alertRule.getAlertDataType()){
            // 设备异常, 查询角色为维修工的人的id
            userIds.addAll(userRoleMapper.selectByRoleName(deviceMaintainerRole));
        }else{
            if (DeviceLocationTypeEnum.FIXED_DEVICE.getOrdinal().equals(deviceDataVo.getLocationType())) {
                //固定设备,床位
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithBed(iotId));
            }else{
                // 随身设备
                userIds.addAll(deviceMapper.selectNursingIdsByIotIdWithElder(iotId));
            }
        }
        // 不管是哪种告警，都要通知管理员
        userIds.addAll(userRoleMapper.selectByRoleName(managerRole));

        // 批量添加告警数据
        List<AlertData> list = insertAlertData(deviceDataVo, alertRule, userIds);

        // 构建websocket消息对象
        AlertNotifyDto alertNotifyDto = BeanUtil.toBean(list.get(0), AlertNotifyDto.class);
        alertNotifyDto.setFunctionName(alertRule.getFunctionName());
        alertNotifyDto.setAlertDataType(list.get(0).getType());
        alertNotifyDto.setNotifyType(1);
        // 使用websocket发消息通知相关用户
        webSocketServer.sendMessageToConsumer(alertNotifyDto, userIds);

    }

    @Resource
    private WebSocketServer webSocketServer;


    @Resource
    private AlertDataMapper alertDataMapper;

    /**
     * 批量添加告警数据， alert_data
     * @param deviceDataVo
     * @param alertRule
     * @param userIds
     */
    private List<AlertData> insertAlertData(DeviceDataVo deviceDataVo, AlertRule alertRule, List<Long> userIds) {
        //字符串拼接告警的原因
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
        // 创建alertData对象
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        // 告警的规则id
        alertData.setAlertRuleId(alertRule.getId());

        alertData.setAlertReason(alertReason);
        // 告警的数据类型，0：老人异常数据，1：设备异常数据
        alertData.setType(alertRule.getAlertDataType());
        // 未处理
        alertData.setStatus(0);
        List<AlertData> list = userIds.stream().map(userId -> {
            //复制对象
            AlertData bean = BeanUtil.toBean(alertData, AlertData.class);
            bean.setUserId(userId);
            return bean;
        }).collect(Collectors.toList());
        //批量添加
        alertDataMapper.batchInsert(list);
        return list;
    }

}
